package com.example.gateway.authorization;

import com.example.common.constant.SecurityConstants;
import com.example.redis.util.RedisUtil;
import com.example.redis.constants.Constant;
import jakarta.annotation.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    @Resource
    private RedisUtil redisUtil;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        // 预检请求放行
        if (request.getMethod() == HttpMethod.OPTIONS){
            return Mono.just(new AuthorizationDecision(true));
        }

        PathMatcher pathMatcher = new AntPathMatcher();
        String method = request.getMethod().name();
        String path = request.getURI().getPath();
        String restfulPath = method + ":" + path;

        // 从Redis中获取当前路径可访问角色列表
        Map<Object, Object> rolesMap = redisUtil.getMap(Constant.RESOURCE_ROLES_MAP);

        // 根据请求路径判断有访问权限的角色列表
        // 拥有访问权限的角色
        List<String> authorizedRoles = new ArrayList<>();
        //是否需要鉴权，默认未设置拦截规则，不需要鉴权
        AtomicBoolean requireCheck = new AtomicBoolean(false);
        rolesMap.forEach((key, value)->{
            if (pathMatcher.match((String) key, restfulPath)){
                List<String> roles = (List<String>) value;
                authorizedRoles.addAll(roles);
                requireCheck.set(true);
            }
        });

        //默认不需要鉴权
        if (!requireCheck.get()){
            return Mono.just(new AuthorizationDecision(true));
        }

        // 认证通过且角色匹配的用户可访问当前路径
        return authentication.filter(Authentication::isAuthenticated)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(authority->{
                    String roleCode = authority.substring(SecurityConstants.AUTHORITY_PREFIX.length());
                    //超级管理员不验证
                    if ("ROOT".equalsIgnoreCase(roleCode)){
                        return true;
                    }
                    return (!CollectionUtils.isEmpty(authorizedRoles)) && authorizedRoles.contains(roleCode);
                })
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
    }
}
